#!/usr/bin/env python

'''
This module implements an ImageSink that posts messages to the Gstreamer bus
when motion (difference) is detected between frames.
'''

import gst
import time

from motiondetector import MotionDetector
from imagesink import ImageSink

class MotionBin(ImageSink):
    '''
    Implements a Gstreamer Bin to detect motion/changes in a video stream.
    
    >>> import gst
    >>> import gobject
    >>> gobject.threads_init()
    >>> import time
    >>> pipeline = gst.Pipeline('test_pipeline')
    >>> source = gst.element_factory_make('videotestsrc', 'source_1')
    >>> motion = MotionBin()
    >>> pipeline.add(source, motion)
    >>> gst.element_link_many(source, motion)
    True
    >>> pipeline.set_state(gst.STATE_PLAYING)
    <enum GST_STATE_CHANGE_ASYNC of type GstStateChangeReturn>
    >>> time.sleep(0.1)
    >>> pipeline.set_state(gst.STATE_NULL)
    <enum GST_STATE_CHANGE_SUCCESS of type GstStateChangeReturn>
    
    @ivar   FRAME_SIZE:     The (width, height) of of frames to detect with.
    @type   FRAME_SIZE:     Tuple
    @ivar   FRAME_RATE:     The frame rate to detect with.  This is an integer
                            representing the number of frames per second.
    @type   FRAME_RATE:     Integer
        
    '''    
    
    FRAME_SIZE = (64,48)            # the size of each pixbuf/frame
    FRAME_RATE = 10                 # controls the number of pixbufs/second
    
    def __init__(self, threshold=0.001, mask_file=None):
        '''
        Initializes a new MotionBin instance.
        
        @param  threshold:  The level above which changes will be considered
                            motion events, in the range of 0 to 1.
        @type   threshold:  Float
        @param  mask_file:  The filename of the image to use to mask off areas 
                            in order to avoid detecting motion in those areas.  
                            the image should have black(opaque) pixels where no 
                            detection should occur and transparent pixels 
                            everywhere else.
        @type   mask_file:  String
        '''
        
        ImageSink.__init__(self, self.FRAME_SIZE, self.FRAME_RATE)
        self.connect('new-frame', self.__detect_motion, 
            self.get_by_name('appsink_pis').get_bus())
        self.detector = MotionDetector(threshold=threshold, mask_file=mask_file)
        
    def __detect_motion(self, img, bus):
        '''
        Converts each frame from a pixbuf to a PIL image then passes it to the
        motion detection algorithm.  if the algorithm has detected motion, post
        a message on the bus.
        
        Posts an application message on the bus containing a 
        motion_detection_results structure if motion was detected

        @param   img:        A PIL Image of the last frame.
        @type    img:        PIL Image
        @param   bus:        The gstreamer bus to post the message to.
        @type    bus:        gst.Bus
        '''

        self.detector.next_image(img)

        # if motion was detected, post a message on the gstreamer bus
        if self.detector.motion_detected:
            struct = gst.Structure('motion_detection_results')
            struct.set_value('value', self.detector.last_value)
            struct.set_value('threshold', self.detector.threshold)
            struct.set_value('time', time.time())
            msg = gst.message_new_application(self, struct)
            bus.post(msg)

if __name__ == '__main__':
    
    import doctest
    doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
